package infrastructure;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import org.apache.log4j.Logger;
import common.messages.KVMessage;

public class HasherService {
	private String myServerInfo;
	private String myUpperHashBound = "";
	private String myLowerHashBound = "";
	private Logger logger = LoggingManager.CreateEcsLogger(HasherService.class);
	private MessageDigest md5;
	private HexBinaryAdapter adapter;
	private int base =16;
	public HasherService() throws NoSuchAlgorithmException{
		md5 = MessageDigest.getInstance("MD5");		
		adapter = new HexBinaryAdapter();
	}
		
	public String getHash(String dataToHash) {
		byte [] array = md5.digest(dataToHash.getBytes());
		String hex = adapter.marshal(array);
		return hex;
	}
	
	public List<String> getUpperBoundHashRangeSorted(int amountOfRanges) {
		List<String> ranges = new ArrayList();
		if (amountOfRanges == 0)
			return ranges;
		String maxNumber = "ffffffffffffffffffffffffffffffff";
		BigInteger maxValue = new BigInteger(maxNumber, base);
		BigInteger countRanges = new BigInteger(Integer.toHexString(amountOfRanges), 16);
		BigInteger rangeSize = maxValue.divide(countRanges);
		BigInteger upperBoundRange = new BigInteger("0",16);
		BigInteger one = new BigInteger(Integer.toHexString(1), 16);
		
		for (int i = 0; i < amountOfRanges; i++){			
			upperBoundRange = upperBoundRange.add(rangeSize);
			ranges.add(upperBoundRange.toString(base));
			upperBoundRange = upperBoundRange.add(one);
		}
		
		/// ONly the upper bounds
		return ranges;
	}
	
	private int compareHashes(String firstHash, String secondHash) {
		BigInteger firstValue = new BigInteger(firstHash, base);
		BigInteger secondValue = new BigInteger(secondHash, base);
		
		return firstValue.compareTo(secondValue);
	}
	
	public boolean isHashInRange(String hash) {//, String lowerBoundHash, String upperBoundHash ){
		logger.info(String.format("Comparing isHashInRange hash %s low %s high %s", hash, this.myLowerHashBound, this.myUpperHashBound));
		return (this.compareHashes(hash, this.myLowerHashBound) >= 0) && (this.compareHashes(hash, this.myUpperHashBound) <=0);
	}
	
	public boolean isHashInRange(String hash, String lowerBoundHash, String upperBoundHash ){
		return (this.compareHashes(hash, lowerBoundHash) >= 0) && (this.compareHashes(hash, upperBoundHash) <0);
	}
	
	/*
	 * This method returns the hashing values. We have included in this class
	 * the upper bound and lower bound of who is using this class.
	 * This class is used by the client, by the server and by the ECS.
	 * The server info is used only by the server and is only useful, if the 
	 * server info is being set previously.This needs to be improved.
	 */
	public Map<String, String> getHashing(String key, String value){
		Map<String, String> metadata = new HashMap<String, String>();
		String [] keys = key.split(KVMessage.STRING_SEPARATOR);
		String [] values = value.split(KVMessage.STRING_SEPARATOR);
		this.myLowerHashBound = "0";
		for (int i =0; i < keys.length; i++) {
			metadata.put(keys[i], values[i]);
			if (this.getMyServerInfo() != null) {
				if (values[i].equals(this.getMyServerInfo())){
					this.myUpperHashBound = keys[i];
					if (i > 1){
						this.myLowerHashBound = keys[i-1]; 
					}
				}
			}
			//logger.info(String.format("i %s hash: %s info: %s", i, keys[i], values[i]));
		}
		
		return metadata;
	}

	
	public String getUpperHashBound() {
		return myUpperHashBound;
	}
	
	public String getLowerHashBound() {
		return myLowerHashBound;
	}
	
	public String getMyServerInfo() {
		return myServerInfo;
	}

	public void setMyServerInfo(String myServerInfo) {
		this.myServerInfo = myServerInfo;
	}
	
	public String getServerInfo(Map<String, String> metadata, String key){
		String lowerBoundHash ="0";
		String hashKeyData = this.getHash(key);
		for (String hashUpperBound:metadata.keySet()){				
			//int result = this.hasherService.compareHashes(hashKeyData, hashUpperBound);
			boolean isInRange = isHashInRange(hashKeyData, lowerBoundHash, hashUpperBound);
			// Update the lower bound
			lowerBoundHash = hashUpperBound;
			if (isInRange){
				return metadata.get(hashUpperBound);
			}
		}
		
		return null;
	}
}
